// Copyright 2011 George Cook
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.


#import "InjectiveContextXMLFactory.h"
#import "TBXML.h"
#import <objc/runtime.h>
#import "InjectivePropertyValueXMLFactory.h"


@interface InjectiveContextXMLFactory ()

- (NSArray *) traverseElement:(TBXMLElement *) root;
- (void) setBasicObjectAttributes:(InjectiveObjectDescription *) objectDescription withObjectElement: (TBXMLElement *) objectElement;
- (void) setPropertyDescriptions:(InjectiveObjectDescription *) objectDescription forPropertyElement: (TBXMLElement *) propertyElement;
- (void) setObjectInitializer:(InjectiveObjectDescription *) objectDescription withObjectElement:(TBXMLElement *) objectElement;

@end

@implementation InjectiveContextXMLFactory
static NSMutableDictionary *registeredPropertyValueFactories;
@synthesize xmlFileName =_xmlFileName;


#pragma mark -
#pragma mark alloc and dealloc

+ (void)initialize
{
	registeredPropertyValueFactories = [[NSMutableDictionary dictionaryWithCapacity:10] retain];
	NSLog(@"Injective Initializing default propertyValueFactories");
	[InjectiveContextXMLFactory registerPropertyValueXMLFactoryFor:@"String"
												  withFactoryClass:@"InjectivePrimitivePropertyValueXMLFactory"];
	[InjectiveContextXMLFactory registerPropertyValueXMLFactoryFor:@"Float"
												  withFactoryClass:@"InjectivePrimitivePropertyValueXMLFactory"];
	[InjectiveContextXMLFactory registerPropertyValueXMLFactoryFor:@"Integer"
												  withFactoryClass:@"InjectivePrimitivePropertyValueXMLFactory"];
	[InjectiveContextXMLFactory registerPropertyValueXMLFactoryFor:@"Array"
												  withFactoryClass:@"InjectiveArrayPropertyValueXMLFactory"];
	[InjectiveContextXMLFactory registerPropertyValueXMLFactoryFor:@"Dictionary"
												  withFactoryClass:@"InjectiveDictionaryPropertyValueXMLFactory"];
	[InjectiveContextXMLFactory registerPropertyValueXMLFactoryFor:@"Boolean"
												  withFactoryClass:@"InjectivePrimitivePropertyValueXMLFactory"];
}

- (void)dealloc
{
	[_xmlFileName release];
	[super dealloc];
}

#pragma mark -
#pragma mark public api

+ (void)registerPropertyValueXMLFactoryFor:(NSString *)propertyValueType withFactoryClass:(NSString *)className
{
	Class factoryClass = NSClassFromString(className);
	if (nil == factoryClass) {
		[[NSException exceptionWithName:@"problem registering propertyvaluexml factory" reason:[NSString stringWithFormat:@"no xml property value class was found for : @%", factoryClass] userInfo:nil] raise];
	}
	
	id instance = class_createInstance(factoryClass, 0);
	if (nil == instance) {
		[[NSException exceptionWithName:@"problem registering propertyvaluexml factory" reason:[NSString stringWithFormat:@"ErroCreatin Instance for : @%", factoryClass] userInfo:nil] raise];
	}
	
	instance = [instance init];
	[registeredPropertyValueFactories setObject:[instance autorelease] forKey:propertyValueType];
}


- (NSArray *) processXMLFile:(NSString *) xmlFilename
{
	NSString * filename = [xmlFilename stringByDeletingPathExtension];
	NSString * extension = [xmlFilename pathExtension];
	
	NSLog(@"loading xml config from %@", [[NSBundle mainBundle] pathForResource:filename ofType:extension]);
	TBXML * tbxml = [[TBXML tbxmlWithXMLFile:xmlFilename] retain];
	TBXMLElement * root = tbxml.rootXMLElement;
	NSArray *objectDescriptions = nil;
	if (root) {
		objectDescriptions = [self traverseElement:tbxml.rootXMLElement];
	}
	[tbxml release];
	return objectDescriptions;
}

- (NSArray *) traverseElement:(TBXMLElement *) root
{
	TBXMLElement * objectElement = [TBXML childElementNamed:@"object" parentElement:root];
	NSMutableArray *objectDescriptions = [[NSMutableArray arrayWithCapacity:10] retain];
	
	while (objectElement != nil) {
		InjectiveObjectDescription * objectDescription = [[InjectiveObjectDescription alloc] init];
		[self setBasicObjectAttributes:objectDescription withObjectElement:objectElement];
		[self setPropertyDescriptions:objectDescription
				   forPropertyElement:[TBXML childElementNamed:@"property" parentElement:objectElement]];
		
		[self setObjectInitializer:objectDescription withObjectElement:objectElement];
		[objectDescriptions addObject:objectDescription];
		[objectDescription release];
		objectElement = [TBXML nextSiblingNamed:@"object" searchFromElement:objectElement];
	}
	NSArray *objectDescriptionsArray = [NSArray arrayWithArray:objectDescriptions];
	[objectDescriptions release];
	return objectDescriptionsArray;
}

- (void) setBasicObjectAttributes:(InjectiveObjectDescription *) objectDescription withObjectElement: (TBXMLElement *) objectElement
{
	objectDescription.isSingleton = [@"true" isEqualToString:[TBXML valueOfAttributeNamed:@"singleton" forElement:objectElement]];
	objectDescription.ID = [TBXML valueOfAttributeNamed:@"id" forElement:objectElement];
	objectDescription.type = [TBXML valueOfAttributeNamed:@"type" forElement:objectElement];
	objectDescription.autoInitialize = [@"true" isEqualToString:[TBXML valueOfAttributeNamed:@"autoInitialize" forElement:objectElement]];
}


- (void) setPropertyDescriptions:(InjectiveObjectDescription *) objectDescription forPropertyElement: (TBXMLElement *) propertyElement
{
	NSMutableArray *propertyDescriptions = [[NSMutableArray arrayWithCapacity:4] retain];
	while (propertyElement != nil) {
		[propertyDescriptions addObject:[self createPropertyDescriptionForPropertyElement:propertyElement]];
		
		propertyElement = [TBXML nextSiblingNamed:@"property" searchFromElement:propertyElement];
	}
	//if the type == nil and value != nil, set the value to the absolute value
	
	if  (propertyDescriptions.count >0) {
		objectDescription.propertyDescriptions = propertyDescriptions;
	}
	[propertyDescriptions release];
	
}

- (InjectivePropertyDescription *) createPropertyDescriptionForPropertyElement: (TBXMLElement *) propertyElement
{
	InjectivePropertyDescription *propertyDescription = [[InjectivePropertyDescription alloc] init];
	
	propertyDescription.idRef = [TBXML valueOfAttributeNamed:@"idref" forElement:propertyElement];
	propertyDescription.type = [TBXML valueOfAttributeNamed:@"type" forElement:propertyElement];
	
	if (nil != propertyDescription.type && (
											nil != [TBXML valueOfAttributeNamed:@"value" forElement:propertyElement]
											||
											nil != [TBXML childElementNamed:@"values" parentElement:propertyElement])
	    ) {
		id <InjectivePropertyValueXMLFactory> valueFactory = [registeredPropertyValueFactories objectForKey:propertyDescription.type];
		
		propertyDescription.value = [valueFactory valueDescriptionForPropertyElement:propertyElement
																	 usingXMLFactory:self
																			withType:propertyDescription.type];
	}
	
	
	propertyDescription.propertyName = [TBXML valueOfAttributeNamed:@"name" forElement:propertyElement];
	return [propertyDescription autorelease];
}

- (void) setObjectInitializer:(InjectiveObjectDescription *) objectDescription withObjectElement:(TBXMLElement *) objectElement
{
	objectDescription.initSelectorName = [TBXML valueOfAttributeNamed:@"initializer" forElement:objectElement];
}
@end
